import { HardwareDevice, HardwareDeviceInterface } from '../libs/hardware_api';
import * as Q from 'q';
import { SerialPort, ReadLineParser } from 'WsSerialPortClient';
import { resolve } from 'url';

(<any>window).CODEMAO_HARDWARE_DEBUG = 1;

export interface MicrobitInterface extends HardwareDeviceInterface {
  write(cmd:string) : Promise<any>;
}

export class Microbit extends HardwareDevice implements MicrobitInterface {
  private board!:SerialPort;
  private parser!:ReadLineParser;
  private reporter:any;
  private pkgn:number;
  private btnValue:number;
  private gesValue:number;
  private touchValue:number;
  private accX:number;
  private accY:number;
  private accZ:number;
  private usValue:number;
  private usPin:string;
  private lock:any;
  private radioRx:string;


  constructor() {
    super();
    SerialPort.initSocket('https://localhost:23321/sp'); // 这个是初始化 硬件助手的连接
    const hardware = this;
    hardware.boardModel = 'uno'; // 这个是 Arduino 的型号，如果提供有误，会导致硬件助手无法刷新成功
    this.onmessage = this.onmessage.bind(this);
    this.pkgn = 0;
    this.reporter = {};
    this.btnValue = 0;
    this.gesValue = 0;
    this.touchValue = 0;
    this.accX = 0;
    this.accY = 0;
    this.accZ = 0;
    this.usValue = -1;
    this.radioRx = '';
    this.usPin = '';
  }

  async reset() {

  }

  destroy() {
    this.removeAllListeners();
  }

  disconnect() {
    const hardware = this;
    return new Promise((resolve, reject) => {
      // hardware.checkServiceStatePromise(reject);
      try {
        hardware.board.close();
      } catch (e) {
        if ((<any>window).CODEMAO_HARDWARE_DEBUG) {
          console.log(e);
        }
      }
      hardware.loopCheckconnection(2000, false).then(() => {
        resolve();
        hardware.onDisconnect();
      }).catch(reject);
    });
  }

  private async checkingFirmware() {
    const hardware = this;
    for (let retry=0; retry<4; retry++){
      await new Promise(resolve => setTimeout(resolve, 500));
      await hardware.write('M0', (data:string) => {
        const val = data.split(' ');
        hardware.firmwareVersion = val[0];
        hardware.firmwareReady = true;
      })
      if (hardware.firmwareReady) return Promise.resolve();
    }
    return Promise.reject();
  }

  // read line callback
  private onmessage (l:string){
    if ((<any>window).CODEMAO_HARDWARE_DEBUG) {
      if (!l.startsWith('@')) console.log('receive: ', l);
    }
    if (l.startsWith('$')){
        clearTimeout(this.lock);
    } else if (l.startsWith('ok')){
        const tmp = l.trim().split(' ');
        const obj = this.reporter[tmp[1]];
        if (obj){
            const timeout = obj[0];
            clearTimeout(timeout);
            const res = obj[1];
            const parser = obj[2];
            let ret = 0;
            if (parser) ret = parser(tmp.slice(2).join(' '));
            res(ret);
            delete this.reporter[tmp[1]];
        }
    } else if (l.startsWith('rs')){
        const tmp = l.trim().split(' ');
        console.log('RESEND', tmp[1]);
        const obj = this.reporter[tmp[1]];
        if (obj) this.sendSync(obj[3]);
    } else if (l.startsWith('@')){
        let tmp = l.trim().split(' ');
        tmp = tmp.filter(n => n !== '');
        this.btnValue = parseInt(tmp[1], 10);
        this.gesValue = parseInt(tmp[2], 10);
        this.touchValue = parseInt(tmp[3], 10);
        this.accX = parseInt(tmp[4], 10);
        this.accY = parseInt(tmp[5], 10);
        this.accZ = parseInt(tmp[6], 10);
        if (tmp.length > 7) {
            this.usValue = parseInt(tmp[7], 10);
        }
    } else if (l.startsWith('M64')){
        const tmp = l.trim().split(' ');
        this.radioRx = tmp[1];
    }
  }

  private sendSync(cmd:string) {
    return new Promise((resolve, reject) => {
      if (this.board) {
        if ((<any>window).CODEMAO_HARDWARE_DEBUG) {
          console.log('send: ', cmd);
        }
        
        if (!cmd.endsWith('\n')) cmd += '\n';
        this.board.write(cmd);
        
      } else {
        reject(new Error('init_error'));
      }
    });
  }
  
  private initDevice(portName:string) {
    return new Promise((resolve, reject) => {
      const hardware = this;
      hardware.board = new SerialPort(portName, { baudRate: 115200 });
      hardware.parser = new SerialPort.parsers.Readline({ delimiter: '\n' });
      hardware.parser.on('data', hardware.onmessage);
      hardware.board.pipe(hardware.parser);
      hardware.pkgn = 0;
      hardware.board.once('open', () => {
        hardware.checkingFirmware().then(() => {
          hardware.emit('connected', hardware);
          hardware.connectionState = true;
          resolve();
        }).catch(reject);
      });
      hardware.board.once('error', (err:any) => {
        if ((<any>window).CODEMAO_HARDWARE_DEBUG) {
          console.log(err);
        }
        hardware.connectionState = false;
        reject(new Error('disconnected'));
      });
      hardware.board.once('close', () => {
        hardware.connectionState = false;
        hardware.onDisconnect();
      });
    });
  }
  write(cmd:string, parser?:any, rstime:number = 1000) {
    const hardware = this;
    return new Promise((resolve, reject) => {
      cmd = `N${this.pkgn} ${cmd}`
      // calc check sum
      cmd += '*';
      let i = 0;
      let checksum = 0;
      while (cmd[i] !== '*'){
          checksum ^= cmd.charCodeAt(i);
          i++;
      }
      cmd += (checksum & 0xff);

      let timeout;
      if (rstime) {
          // only necessary for serial
          this.lock = setTimeout(() => {
              this.sendSync(cmd);
          }, 100);
          timeout = setTimeout(() => {
              // this.session.write(data);
              resolve(-1);
              console.log('---------- BREAK ---------');
          }, rstime);
      }

      this.reporter[`N${this.pkgn}`] = [timeout, resolve, parser, cmd];

      hardware.pkgn += 1;
      if (hardware.pkgn === 100) hardware.pkgn = 0;
      
      this.sendSync(cmd);
    });
  }
  connect(device:string, options?:any) {
    const hardware = this;
    return new Promise((resolve, reject) => {
      // hardware.checkServiceStatePromise(reject);
      options = options || {};
      hardware.deviceName = device;
      hardware.initDevice(device).then(resolve).catch(reject);
    });
  }
}



